package com.share.device.receiver;

import com.alibaba.fastjson.JSONObject;
import com.rabbitmq.client.Channel;
import com.share.common.rabbit.constant.MqConst;
import com.share.device.domain.CabinetSlot;
import com.share.device.service.IDeviceService;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;


@Slf4j
@Component
@RequiredArgsConstructor
public class DeviceReceiver {

    /**
     * 设备服务接口，用于处理实际的设备操作业务逻辑
     */
    private final IDeviceService deviceService;

    /**
     * Redis模板，用于实现分布式锁，防止重复处理消息
     */
    private final RedisTemplate redisTemplate;

    /**
     * 监听解锁充电宝卡槽的消息
     * 使用RabbitListener注解绑定到指定的交换机、队列和路由键
     *
     * @param content 消息内容（JSON格式字符串）
     * @param message 原始消息对象，包含消息属性等信息
     * @param channel 消息通道，用于手动确认消息
     */
    @SneakyThrows // Lombok注解，自动处理异常抛出
    @RabbitListener(bindings = @QueueBinding(exchange = @Exchange(value = MqConst.EXCHANGE_DEVICE, durable = "true"), // 绑定的交换机，持久化
            value = @Queue(value = MqConst.QUEUE_UNLOCK_SLOT, durable = "true"),     // 绑定的队列，持久化
            key = MqConst.ROUTING_UNLOCK_SLOT)                                       // 绑定的路由键
    )
    public void unlockSlot(String content, Message message, Channel channel) {
        log.info("[设备服务]接收到解锁充电宝卡槽消息：{}", content);

        // 将JSON格式的消息内容转换为CabinetSlot对象
        CabinetSlot cabinetSlot = JSONObject.parseObject(content, CabinetSlot.class);

        // 生成Redis分布式锁的键，格式：unlock:slot:设备ID:卡槽编号
        String key = "unlock:slot:" + cabinetSlot.getCabinetId() + ":" + cabinetSlot.getSlotNo();

        // 使用setIfAbsent实现分布式锁，防止重复处理同一请求
        // 若键不存在则设置成功，返回true；若已存在则返回false
        // 锁的过期时间为1小时，避免死锁
        boolean isExist = redisTemplate.opsForValue().setIfAbsent(key, cabinetSlot.getSlotNo(), 1, TimeUnit.HOURS);

        // 如果锁已存在，说明是重复请求，直接返回
        if (!isExist) {
            log.info("检测到重复的解锁请求: {}", content);
            return;
        }

        try {
            // 调用设备服务的方法执行实际的解锁操作
            deviceService.unlockSlot(cabinetSlot);

            // 手动确认消息已被成功处理，参数：消息标签，是否批量确认
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
            log.info("[设备服务]解锁充电宝卡槽成功：{}", content);
        } catch (Exception e) {
            // 处理异常情况，记录错误日志
            log.error("设备服务：解锁充电宝卡槽失败：{}", content, e);

            // 出现异常时删除分布式锁，允许后续重试
            redisTemplate.delete(key);

            // 手动拒绝消息并使其重新入队，参数：消息标签，是否批量处理，是否重新入队
            channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
        }
    }
}
